#include <pthread.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <vpi_user.h>

#include "cpu.h"

pthread_t cpu_thread;
sem_t mem_sem, cpu_sem;

#if 0
static int bind_socket(void)
{
	int sockfd;
	socklen_t size;
	struct sockaddr_un server_addr;

	sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
	if (sockfd < 0) {
		perror("socket");
		return -1;
	}
	memset(&server_addr, 0, sizeof server_addr);
	server_addr.sun_family = AF_UNIX;
	sprintf(server_addr.sun_path, "/tmp/armemu-mem%u", getpid());
	size = offsetof(struct sockaddr_un, sun_path) + strlen(server_addr.sun_path);
	if (bind(sockfd, (struct sockaddr *) &server_addr, size) < 0) {
		perror("bind");
		return -1;
	}
	return sockfd;
}
#endif

PLI_INT32 calltf_emu_init(PLI_BYTE8 *_)
{
#if 0
	struct sockaddr_un client_addr;
	socklen_t size;

	mem_socket = bind_socket();
	if (mem_socket < 0) {
		abort();
	}
#endif
	sem_init(&mem_sem, 0, 0);
	sem_init(&cpu_sem, 0, 0);
	pthread_create(&cpu_thread, NULL, cpu_proc, NULL);
	cpu_init();

#if 0
	memset(&client_addr, 0, sizeof client_addr);
	client_addr.sun_family = AF_UNIX;
	sprintf(client_addr.sun_path, "/tmp/armemu-cpu%u", getpid());
	size = offsetof(struct sockaddr_un, sun_path) + strlen(client_addr.sun_path);
	if (connect(mem_socket, (struct sockaddr *) &client_addr, size) < 0) {
		perror("connect");
		abort();
	}
#endif

	return 0;
}

PLI_INT32 calltf_emu_run(PLI_BYTE8 *_)
{
	vpiHandle callh, args_iter, argh;
	s_vpi_value val_in, val_out;
	p_vpi_vecval vec_in;
	s_vpi_vecval vec_out[4] = {0};

	callh = vpi_handle(vpiSysTfCall, NULL);
	args_iter = vpi_iterate(vpiArgument, callh);

	argh = vpi_scan(args_iter);
	val_in.format = vpiVectorVal;
	vpi_get_value(argh, &val_in);
	vec_in = val_in.value.vector;

	bus_in[0] = vec_in[0].aval;
	bus_in[1] = vec_in[1].aval;
	bus_in[2] = vec_in[2].aval;

	vpi_free_object(args_iter);

	sem_post(&cpu_sem);
	sem_wait(&mem_sem);

	vec_out[0].aval = bus_out[0];
	vec_out[1].aval = bus_out[1];
	vec_out[2].aval = bus_out[2];
	vec_out[3].aval = bus_out[3];
	val_out.format = vpiVectorVal;
	val_out.value.vector = vec_out;
	vpi_put_value(callh, &val_out, 0, vpiNoDelay);

	return 0;
}

PLI_INT32 sizetf_emu_run(PLI_BYTE8 *_)
{
	return 128;
}

#if 0
PLI_INT32 calltf_emu_gpr_name(PLI_BYTE8 *_)
{
	vpiHandle callh, args_iter, argh;
	s_vpi_value val_in, val_out;
	p_vpi_vecval vec_in;
	int index;
	uint8_t mode;
	const char *s;

	callh = vpi_handle(vpiSysTfCall, NULL);
	args_iter = vpi_iterate(vpiArgument, callh);

	argh = vpi_scan(args_iter);
	val_in.format = vpiVectorVal;
	vpi_get_value(argh, &val_in);
	vec_in = val_in.value.vector;
	index = vec_in->aval;

	argh = vpi_scan(args_iter);
	val_in.format = vpiVectorVal;
	vpi_get_value(argh, &val_in);
	vec_in = val_in.value.vector;
	mode = vec_in->aval;

	vpi_free_object(args_iter);

	s = get_gpr_name(index, mode);
	val_out.format = vpiStringVal;
	val_out.value.str = s;
	vpi_put_value(callh, &val_out, 0, vpiNoDelay);

	return 0;
}

PLI_INT32 sizetf_emu_gpr_name(PLI_BYTE8 *_)
{
	return 56;
}
#endif

void register_systf(void)
{
	s_vpi_systf_data tf = {0};

	tf.type = vpiSysTask;
	tf.sysfunctype = vpiSysTask;
	tf.tfname = "$emu_init";
	tf.calltf = calltf_emu_init;
	vpi_register_systf(&tf);

	tf.type = vpiSysFunc;
	tf.sysfunctype = vpiSizedFunc;

	tf.tfname = "$emu_run";
	tf.calltf = calltf_emu_run;
	tf.sizetf = sizetf_emu_run;
	vpi_register_systf(&tf);
}

void (*vlog_startup_routines[])(void) = {
	register_systf,
	0
};
